#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <sys/time.h>

#define PACK_LEN 72
#define BUFSIZE  4096

void tvsub(struct timeval *out, struct timeval *in) {
	out->tv_sec -= in->tv_sec;
}

u_short checksum(u_short *data, int len) {
	u_long sum = 0;

	for ( ; len>1; len-=2) {
		sum += *data++;
		if ( sum & 0x80000000 )
			sum = (sum & 0xffff) + (sum>>16);
	}
	
	if ( len == 1 ) {
		u_short i = 0;
		*(u_char*)(&i) = *(u_char*)data;
		sum += i;
	}

	while ( sum >> 16 )
		sum = (sum & 0xffff) + (sum>>16);
	
	return (sum==0xffff) ? sum : ~sum;
}

// 生成ICMP数据包
void make_icmp_packet(struct icmp *icmp, int len, int n) {
	memset(icmp, 0x00, len);
	gettimeofday((struct timeval*)(icmp->icmp_data), NULL);
	icmp->icmp_type = ICMP_ECHO;
	icmp->icmp_code = 0;
	icmp->icmp_id   = getpid();
	icmp->icmp_seq  = n;
	icmp->icmp_cksum = 0;
	icmp->icmp_cksum = checksum((u_short*)icmp, len);
}

int main( void ) {
	struct sockaddr_in send_sa;
	int i, j;
	int sfd;
	char send_buf[PACK_LEN];
	char recv_buf[BUFSIZE];
	struct in_addr start_addr, end_addr;
	struct timeval tv;
	fd_set readfd_set;
	struct ip   *ip;
	struct icmp *icmp;
	int len;

	send_sa.sin_family = AF_INET;
	sfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
	if ( sfd == -1 ) {
		perror("socket");
		return 1;
	}
	
	printf("请输入网段(192.168.0):");
	char buf[32] = "192.168.0";
	fgets(buf, 32, stdin);
	buf[strlen(buf)-1] = 0;

	char ip_addr[17];
	for (i=1; i<=254; i++) {
		memset(ip_addr, 0x00, sizeof(ip_addr));
		sprintf(ip_addr, "%s.%d", buf, i);
		send_sa.sin_addr.s_addr = inet_addr(ip_addr);
		printf("scan %s\n", inet_ntoa(send_sa.sin_addr));
		for (j=0; j<3; j++) {
			make_icmp_packet((struct icmp*)send_buf, PACK_LEN, j);
			if ( sendto(sfd, send_buf, PACK_LEN, 0, (struct sockaddr*)&send_sa, sizeof(send_sa)) == -1 ) {
				perror("sendto");
			}
			tv.tv_sec = 0;
			tv.tv_usec = 200*1000;
			FD_ZERO(&readfd_set);
			FD_SET(sfd, &readfd_set);
			while ( 1 ) {
				if ( select(sfd+1, &readfd_set, NULL, NULL, &tv) <= 0 )
					break;
				if ( recvfrom(sfd, recv_buf, BUFSIZE, 0, NULL, NULL) == -1 ) {
					perror("recvfrom");
					exit(0);
				}
				ip = (struct ip*)recv_buf;
				len = ip->ip_hl << 2;
				if ( ip->ip_src.s_addr == send_sa.sin_addr.s_addr ) {
					icmp = (struct icmp*)(recv_buf+len);
					if ( icmp->icmp_type == ICMP_ECHOREPLY ) {
						printf("%-15s", inet_ntoa(*(struct in_addr*)&(ip->ip_src.s_addr)));
						gettimeofday(&tv, NULL);
						tvsub(&tv, (struct timeval*)(icmp->icmp_data));
						printf(":RTT=%8.4d ms\n", tv.tv_sec+tv.tv_usec/1000);
						goto label;
					} else {
						printf("ICMP STATE:%d\n", icmp->icmp_type);
					}
				}
			}
		}
label:
		continue;	
	}

	close(sfd);
}

